使用Unity制作起雾的窗户效果着色器
本教程由游戏开发者Linden Reid介绍如何制作着色器实现起雾的窗户效果,主要分为三部分内容:
高斯模糊效果
读写纹理
根据纹理修改模糊效果
我们在每一部分的最后会提供可以使用的着色器,你可以从中学习方法,以便在制作其它着色器时重用或改写。
获取实现水雾窗户效果的着色器代码:
https://github.com/lindenreid/Unity-Shader-Tutorials/blob/master/Assets/Materials/Shaders/window.shader
高斯模糊
窗户的起雾效果通过高斯模糊和往上面添加轻微的着色来实现。
我们会通过使用GrabPass标签,获取已经在摄像机渲染的窗户后面的像素,然后对这些像素应用高斯模糊算法。
很多文章讲解过高斯模糊的实现原理,本文使用了《GLSL代码的高斯模糊教程》来编写自定义的着色器:
https://github.com/mattdesl/lwjgl-basics/wiki/ShaderLesson5
使用GrabPass
我们需要获取窗户后面的像素,以便对其进行模糊处理,我们可以使用Unity的GrabPass。
GrabPass将在对象后渲染的像素绘制到着色器可以访问的纹理上。使用该渲染批次时,我们需要使用SubShader代码块中的GrabPass 标签。
SubShader
{
//绘制透明窗户也很重要
Tags
{
"Queue" = "Transparent"
}
//将对象后的屏幕内容抓取到_BGTex中
GrabPass
{
"_BGTex"
}
// … 其它着色器代码…
接下来,在CGPROGRAM标签中,确保已经包含Unity.cginc文件,从而使用读取GrabPass的特别函数。
#include "UnityCG.cginc"
为了能够读取GrabPass纹理,我们需要合适的纹理坐标。
Unity通过ComputeScreenGrabPos函数非常简单的获得坐标,只要将剪辑空间顶点位置作为输入提供,该函数就能够给出合适的纹理坐标来读取GrabPass纹理。我们可以在顶点着色器中进行这项计算。
vertexOutput output;
output.pos = UnityObjectToClipPos(input.vertex);
output.grabPos = ComputeGrabScreenPos(output.pos);
应用模糊效果
现在,我们可以在片元着色器Fragment Shader中读取纹理,然后应用模糊效果。
我们通过以下参数编写模糊算法。
float4 gaussianBlur(
float2 dir,
float4 grabPos,
float res,
sampler2D tex,
float radius
)
{
//模糊算法在此编写
}
该算法会获取GrabPass纹理,即“tex”,应用模糊效果,并返回float4类型的像素颜色。
下面介绍每个参数的含义:
float2 dir:模糊效果将应用于两个通道,所以我们需要“dir”即方向参数。效果会在X方向和Y方向各应用一次,因为我们使用(1, 0)表示X方向,(0, 1)表示Y方向,所以获得的是Float2类型。
float4 grabPos:grabPos变量表示模糊像素的纹理坐标。
float res:res变量表示X轴和Y轴上的纹理分辨率。
sampler2D tex:该变量表示要模糊的纹理。我们需要整个纹理,因为模糊算法会对原始像素附近的像素进行采样。
float radius:该变量表示从原始像素到模糊位置的距离。数值越大,模糊效果越强。
接下来,让我们定义控制模糊效果所需的参数。
我们需要一个浮点数定义模糊强度,我们将其定义为_BlurRadius ,并在着色器代码的开始将该变量公开给属性块的材质。
我们还需要GrabPass纹理,该纹理的名称要和GrabPass 标签中的名称相同,本示例中为_BGTex。我们可以通过创建_YourTextureName_TexelSize属性来获取需要纹理的大小信息。
我们给模糊效果加入了深蓝色着色,使效果更明显。如果想使用该颜色,请添加颜色到属性中,我们将其命名为_FogColor。
//属性
//在材质设置
uniform float4 _FogColor;
uniform float _BlurRadius;
//获取通道
uniform sampler2D _BGTex;
uniform float4 _BGTex_TexelSize;
现在,我们得到了将模糊效果应用到背景纹理的所需信息。
我们打算将模糊效果应用到两个渲染批次:一个在X方向,另一个在Y方向。通常,我们会让第二个模糊渲染批次处理第一个模糊的结果,而不是处理原始背景图像。但这样需要更多着色器,过程也会更复杂。
所以,我对模糊的处理比较简单,在两个方向模糊了原始图像并添加效果。该方法的缺点是:1、模糊的质量较低。2、模糊部分比原始图像更亮,因为添加了效果。
我们将模糊部分乘以着色颜色。请注意,_TexelSize 在.zw属性中包含纹理的xy大小。
float4 blurX = gaussianBlur(float2(1,0),
input.grabPos,
_BGTex_TexelSize.z,
_BGTex,
_BlurRadius);
float4 blurY = gaussianBlur(float2(0,1),
input.grabPos,
_BGTex_TexelSize.w,
_BGTex,
_BlurRadius);
return (blurX + blurY) * _FogColor;
效果
我们将该着色器应用到材质上,并将其附加到场景的一个平坦表面上,现在着色器实现了基本的模糊效果。
下图是起雾窗户效果的预览。
读写纹理
为了按照鼠标交互改变着色器效果,我们需要将鼠标移动写入纹理,并在着色器读取该纹理。
从着色器读取纹理
首先,我们在着色器中创建名为_MouseMap 的sampler2D属性。
uniform sampler2D _MouseMap;
在片元着色器中,绘制该纹理以便调试。
float4 mouseSample = tex2D(_MouseMap, input.texCoord.xy);
以上就是片元着色器的功能,用于实现纹理的读写过程。对_MouseMap属性进行编写前,我们将得到不透明灰色平面,如下所示。
使用C#代码写入纹理
为了写入纹理,我们需要创建C#脚本,并将脚本附加到平面。
我们可以通过C#代码的Material.Set函数,设置着色器属性。只需要让属性的字符串名称对应在着色器的对应名称即可。
public class DrawOnTexture : MonoBehaviour {
//在检视窗口设置
public Renderer destinationRenderer;
public int TextureSize;
public Color BlurColor;
private Texture2D texture;
void Start ()
{
//新建Texture2D
texture = new Texture2D(TextureSize, TextureSize, TextureFormat.RFloat, false, true);
//将所有像素值设为默认颜色
for (int i = 0; i < texture.height; i++)
{
for (int j = 0; j < texture.width; j++)
{
texture.SetPixel(i, j, BlurColor);
}
}
//应用SetPixel的属性
texture.Apply();
//将纹理信息传到材质
destinationRenderer.material.SetTexture("_MouseMap", texture);
}
}
我们为BlurColor选取了黑色,所以运行时场景效果如下图所示。
写入鼠标位置
现在,我们添加一个OnMouseDrag()函数,当玩家点击划动平面时,在鼠标位置周围绘制圆圈。请将MeshCollider组件附加到平面对象,使它接收OnMouseDrag()事件。
void OnMouseDrag ()
{
//从鼠标位置向屏幕创建光线
//然后测试对纹理的碰撞效果
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray, out hit, 100))
{
Color color = new Color(1, 0, 0, 1);
//把纹理坐标转换为像素坐标
int x = (int)(hit.textureCoord.x * texture.width);
int y = (int)(hit.textureCoord.y * texture.height);
//写入被碰到的像素
texture.SetPixel(x, y, color);
//写入Radius范围内的相邻像素
for (int i = 0; i < texture.height; i++)
{
for (int j = 0; j < texture.width; j++)
{
float dist = Vector2.Distance(new Vector2(i,j),
new Vector2(x,y)
);
if(dist <= Radius)
texture.SetPixel(i, j, color);
}
}
//应用改动并告知着色器
texture.Apply();
destinationRenderer.material.SetTexture("_MouseMap", texture);
}
}
现在运行游戏,我们应该能在纹理上使用鼠标进行绘图了。
根据纹理修改模糊效果
现在,我们可以根据刚创建的鼠标拖动纹理来改变模糊效果。
根据鼠标拖动纹理应用模糊效果
我们返回到着色器部分,根据从纹理读取的数值应用模糊部分。由于我们在鼠标点击的位置绘制了红色,而且纹理默认是黑色,因此我们可以根据红色通道修改模糊和着色量。
我们要进行以下乘法。
_BlurRadius * (1 - red channel)
由于红色通道的数值在0~1之间,因此红色数值越大,模糊的半径越小。这种情况下,红色通道会是0或1,所以它会在红色绘制的位置移除模糊效果。
着色颜色同理,只不过需要在未应用起雾效果的部分定义_ClearColor。
// r = 1表示鼠标点击
// r = 0表示没有鼠标操作
float blurRadius = _BlurRadius * (1-mouseSample.r);
float4 color = mouseSample.r*_ClearColor + (1.0-mouseSample.r)*_FogColor;
float4 blurX = gaussianBlur(float2(1,0),
input.grabPos,
_BGTex_TexelSize.z,
_BGTex,
blurRadius);
float4 blurY = gaussianBlur(float2(0,1),
input.grabPos,
_BGTex_TexelSize.w,
_BGTex,
blurRadius);
return (blurX + blurY) * color;
现在,我们可以在窗口进行绘制,点击的位置将消退模糊和着色效果。
我们已经得到了不错的窗户起雾着色器。但是为什么不做的更复杂一些呢?
时间算法
在处理着色器前,请思考一下算法的原理。基本上,我们需要根据点击指定像素的时间,来修改模糊量。像素时间值越小,表示它被点击的时间越近,因此起雾效果较小。
我们还需要最大持续时间来定义像素恢复起雾效果的速度。该值将用于把时间转换为标准化数值,即0~1,用于调整最小值和最大值之间的模糊量。
算法如下所示。
age = current time - time drawn
percent max age = age / max age
然后,我们将标准化的“percent max age”值应用到模糊半径和着色。像素时间值越小,百分比最大持续时间越小,从而使模糊强度越小。
类似地,我们会根据percent max age 值,使用较小的着色颜色量和较大的清晰颜色。
blur radius = max radius * percent max age
tint = (1 - percent max age)*(clear color) + (percent max age)*(fog color)
应用时间
为了将其应用于着色器,首先我们将像素绘制时间写入鼠标贴图纹理的r通道,而不是只写入1.0。
Color color = new Color(Time.timeSinceLevelLoad, 0, 0, 1);
接下来,在着色器应用之前的算法,获取percent max age值。
//从鼠标点击纹理获取像素绘制的时间
float timeDrawn = tex2D(_MouseMap, input.texCoord.xy).r;
//时间 = 当前时间 - 绘制时间
float age = clamp(_Time.y - timeDrawn, 0.0001, _Time.y);
//百分比最大时间 = 时间/最大时间
float percentMaxAge = saturate(age / _MaxAge);
最后,我们将percent max age值应用到模糊半径和着色颜色。
// 时间越长表示百分比最大时间越大,从而有更大的模糊效果
float blurRadius = _BlurRadius * percentMaxAge;
float4 color = (1-percentMaxAge)*_ClearColor + percentMaxAge*_FogColor;
现在,模糊效果会根据定义的最大持续时间进行恢复。如下图所示,我们将_MaxAge设为1秒,使模糊效果快速淡化。
结语
本教程介绍了如何将颜色之外的信息编码到纹理中,以及如何利用该方法实现不错的效果。
获取水雾窗户效果的着色器代码:
https://github.com/lindenreid/Unity-Shader-Tutorials/blob/master/Assets/Materials/Shaders/window.shader
下载Unity Connect APP,请点击此处。 观看部分Unity官方视频,请关注B站帐户:Unity官方。
你可以访问Unity答疑专区留下你的问题,Unity社区和官方团队帮你解答:
Connect.unity.com/g/discussion
推荐阅读
在Unity 2019.2中扩展Shader Graph,实现自定义光照使用Unity实现魔法火焰效果使用Visual Effect Graph提升FPS示例项目的视觉效果
Unity着色器教程 | 积雪效果
使用Unity开发沙盒游戏《The Serpent Rogue》
在Unity实现游戏命令模式
高级动画绑定功能:角色与物品的交互
使用Unity AR Foundation在增强现实中查看模型
喜欢本文,请点“在看”